<!DOCTYPE html>
<meta charset="utf-8" />
<meta
  name="viewport"
  content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<title>d3-simple-slider</title>

<script src="https://d3js.org/d3.v6.min.js"></script>
<script src="https://unpkg.com/d3-simple-slider"></script>

<link
  rel="stylesheet"
  href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
  integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
  crossorigin="anonymous"
/>

<div class="container">
  <h1>Basic functionality</h1>
  <h2>Simple</h2>
  <div class="row align-items-center">
    <div class="col-sm-2"><p id="value-simple"></p></div>
    <div class="col-sm"><div id="slider-simple"></div></div>
  </div>
  <h2>Step</h2>
  <div class="row align-items-center">
    <div class="col-sm-2"><p id="value-step"></p></div>
    <div class="col-sm"><div id="slider-step"></div></div>
  </div>
  <h2>Time</h2>
  <div class="row align-items-center">
    <div class="col-sm-2"><p id="value-time"></p></div>
    <div class="col-sm"><div id="slider-time"></div></div>
  </div>
  <h2>Fill</h2>
  <div class="row align-items-center">
    <div class="col-sm-2"><p id="value-fill"></p></div>
    <div class="col-sm"><div id="slider-fill"></div></div>
  </div>
  <h2>Range</h2>
  <div class="row align-items-center">
    <div class="col-sm-2"><p id="value-range"></p></div>
    <div class="col-sm"><div id="slider-range"></div></div>
  </div>
  <h2>Vertical</h2>
  <div class="row align-items-center">
    <div class="col-sm-2"><p id="value-vertical"></p></div>
    <div class="col-sm"><div id="slider-vertical"></div></div>
  </div>
  <h1>Extended functionality</h1>
  <h2>Alternative handle</h2>
  <div class="row align-items-center">
    <div class="col-sm-2"><p id="value-alternative-handle"></p></div>
    <div class="col-sm"><div id="slider-alternative-handle"></div></div>
  </div>
  <h2>Transition</h2>
  <div class="row align-items-center">
    <div class="col-sm-2"><p id="value-transition"></p></div>
    <div class="col-sm"><div id="slider-transition"></div></div>
  </div>
  <h1>Examples</h1>
  <h2>New York Times</h2>
  <div class="row align-items-center">
    <div class="col-sm-2"><p id="value-new-york-times"></p></div>
    <div class="col-sm"><div id="slider-new-york-times"></div></div>
  </div>
  <h2>Color picker</h2>
  <div class="row align-items-center">
    <div class="col-sm-2"><p id="value-color-picker"></p></div>
    <div class="col-sm"><div id="slider-color-picker"></div></div>
  </div>
</div>

<script>
  // Simple
  var data = [0, 0.005, 0.01, 0.015, 0.02, 0.025];

  var sliderSimple = d3
    .sliderBottom()
    .min(d3.min(data))
    .max(d3.max(data))
    .width(300)
    .tickFormat(d3.format('.2%'))
    .ticks(5)
    .default(0.015)
    .on('onchange', val => {
      d3.select('p#value-simple').text(d3.format('.2%')(val));
    });

  var gSimple = d3
    .select('div#slider-simple')
    .append('svg')
    .attr('width', 500)
    .attr('height', 100)
    .append('g')
    .attr('transform', 'translate(30,30)');

  gSimple.call(sliderSimple);

  d3.select('p#value-simple').text(d3.format('.2%')(sliderSimple.value()));

  // Step
  var sliderStep = d3
    .sliderBottom()
    .min(d3.min(data))
    .max(d3.max(data))
    .width(300)
    .tickFormat(d3.format('.2%'))
    .ticks(5)
    .step(0.005)
    .default(0.015)
    .on('onchange', val => {
      d3.select('p#value-step').text(d3.format('.2%')(val));
    });

  var gStep = d3
    .select('div#slider-step')
    .append('svg')
    .attr('width', 500)
    .attr('height', 100)
    .append('g')
    .attr('transform', 'translate(30,30)');

  gStep.call(sliderStep);

  d3.select('p#value-step').text(d3.format('.2%')(sliderStep.value()));

  // Time
  var dataTime = d3.range(0, 10).map(function(d) {
    return new Date(1995 + d, 10, 3);
  });

  var sliderTime = d3
    .sliderBottom()
    .min(d3.min(dataTime))
    .max(d3.max(dataTime))
    .step(1000 * 60 * 60 * 24 * 365)
    .width(300)
    .tickFormat(d3.timeFormat('%Y'))
    .tickValues(dataTime)
    .default(new Date(1998, 10, 3))
    .on('onchange', val => {
      d3.select('p#value-time').text(d3.timeFormat('%Y')(val));
    });

  var gTime = d3
    .select('div#slider-time')
    .append('svg')
    .attr('width', 500)
    .attr('height', 100)
    .append('g')
    .attr('transform', 'translate(30,30)');

  gTime.call(sliderTime);

  d3.select('p#value-time').text(d3.timeFormat('%Y')(sliderTime.value()));

  // Fill
  var sliderFill = d3
    .sliderBottom()
    .min(d3.min(data))
    .max(d3.max(data))
    .width(300)
    .tickFormat(d3.format('.2%'))
    .ticks(5)
    .default(0.015)
    .fill('#2196f3')
    .on('onchange', val => {
      d3.select('p#value-fill').text(d3.format('.2%')(val));
    });

  var gFill = d3
    .select('div#slider-fill')
    .append('svg')
    .attr('width', 500)
    .attr('height', 100)
    .append('g')
    .attr('transform', 'translate(30,30)');

  gFill.call(sliderFill);

  d3.select('p#value-fill').text(d3.format('.2%')(sliderFill.value()));

  // Range
  var sliderRange = d3
    .sliderBottom()
    .min(d3.min(data))
    .max(d3.max(data))
    .width(300)
    .tickFormat(d3.format('.2%'))
    .ticks(5)
    .default([0.015, 0.02])
    .fill('#2196f3')
    .on('onchange', val => {
      d3.select('p#value-range').text(val.map(d3.format('.2%')).join('-'));
    });

  var gRange = d3
    .select('div#slider-range')
    .append('svg')
    .attr('width', 500)
    .attr('height', 100)
    .append('g')
    .attr('transform', 'translate(30,30)');

  gRange.call(sliderRange);

  d3.select('p#value-range').text(
    sliderRange
      .value()
      .map(d3.format('.2%'))
      .join('-')
  );

  // Vertical
  var sliderVertical = d3
    .sliderLeft()
    .min(d3.min(data))
    .max(d3.max(data))
    .height(300)
    .tickFormat(d3.format('.2%'))
    .ticks(5)
    .default(0.015)
    .on('onchange', val => {
      d3.select('p#value-vertical').text(d3.format('.2%')(val));
    });

  var gVertical = d3
    .select('div#slider-vertical')
    .append('svg')
    .attr('width', 100)
    .attr('height', 400)
    .append('g')
    .attr('transform', 'translate(60,30)');

  gVertical.call(sliderVertical);

  d3.select('p#value-vertical').text(d3.format('.2%')(sliderVertical.value()));

  // Alternative handle
  var sliderAlternativeHandle = d3
    .sliderBottom()
    .min(d3.min(data))
    .max(d3.max(data))
    .width(300)
    .tickFormat(d3.format('.2%'))
    .ticks(5)
    .default(0.015)
    .handle(
      d3
        .symbol()
        .type(d3.symbolCircle)
        .size(200)()
    )
    .on('onchange', val => {
      d3.select('p#value-alternative-handle').text(d3.format('.2%')(val));
    });

  var g2 = d3
    .select('div#slider-alternative-handle')
    .append('svg')
    .attr('width', 500)
    .attr('height', 100)
    .append('g')
    .attr('transform', 'translate(30,30)');

  g2.call(sliderAlternativeHandle);

  d3.select('p#value-alternative-handle').text(sliderAlternativeHandle.value());

  // Transition
  var sliderTransition = d3
    .sliderBottom()
    .min(d3.min(data))
    .max(d3.max(data))
    .width(300)
    .tickFormat(d3.format('.2%'))
    .ticks(5)
    .default(0.015)
    .on('onchange', val => {
      d3.select('p#value-transition').text(d3.format('.2%')(val));
    });

  var gTransition = d3
    .select('div#slider-transition')
    .append('svg')
    .attr('width', 500)
    .attr('height', 100)
    .append('g')
    .attr('transform', 'translate(30,30)');

  gTransition.call(sliderTransition);

  setInterval(() => {
    sliderTransition.width(Math.random() * 100 + 200);

    gTransition
      .transition()
      .duration(200)
      .call(sliderTransition);
  }, 1000);

  d3.select('p#value-transition').text(
    d3.format('.2%')(sliderTransition.value())
  );

  // New York Times
  var width = 565;
  var height = 120;
  var margin = { top: 20, right: 50, bottom: 50, left: 40 };

  var dataNewYorkTimes = d3.range(1, 41).map(d => ({
    year: d,
    value: 10000 * Math.exp(-(d - 1) / 40),
  }));

  var svg = d3
    .select('div#slider-new-york-times')
    .append('svg')
    .attr('width', width)
    .attr('height', height);

  var padding = 0.1;

  var xBand = d3
    .scaleBand()
    .domain(dataNewYorkTimes.map(d => d.year))
    .range([margin.left, width - margin.right])
    .padding(padding);

  var xLinear = d3
    .scaleLinear()
    .domain([
      d3.min(dataNewYorkTimes, d => d.year),
      d3.max(dataNewYorkTimes, d => d.year),
    ])
    .range([
      margin.left + xBand.bandwidth() / 2 + xBand.step() * padding - 0.5,
      width -
        margin.right -
        xBand.bandwidth() / 2 -
        xBand.step() * padding -
        0.5,
    ]);

  var y = d3
    .scaleLinear()
    .domain([0, d3.max(dataNewYorkTimes, d => d.value)])
    .nice()
    .range([height - margin.bottom, margin.top]);

  var yAxis = g =>
    g
      .attr('transform', `translate(${width - margin.right},0)`)
      .call(
        d3
          .axisRight(y)
          .tickValues([1e4])
          .tickFormat(d3.format('($.2s'))
      )
      .call(g => g.select('.domain').remove());

  var slider = g =>
    g.attr('transform', `translate(0,${height - margin.bottom})`).call(
      d3
        .sliderBottom(xLinear)
        .step(1)
        .ticks(4)
        .default(9)
        .on('onchange', value => draw(value))
    );

  var bars = svg
    .append('g')
    .selectAll('rect')
    .data(dataNewYorkTimes);

  var barsEnter = bars
    .enter()
    .append('rect')
    .attr('x', d => xBand(d.year))
    .attr('y', d => y(d.value))
    .attr('height', d => y(0) - y(d.value))
    .attr('width', xBand.bandwidth());

  svg.append('g').call(yAxis);
  svg.append('g').call(slider);
  
  svg.select('.track-overlay').attr('stroke-width', 120); // Ensure drag zone covers everything

  var draw = selected => {
    barsEnter
      .merge(bars)
      .attr('fill', d => (d.year === selected ? '#bad80a' : '#e0e0e0'));

    d3.select('p#value-new-york-times').text(
      d3.format('$,.2r')(dataNewYorkTimes[selected - 1].value)
    );
  };

  draw(9);

  // Color picker
  var num2hex = rgb => {
    return rgb
      .map(color => {
        let str = color.toString(16);

        if (str.length === 1) {
          str = '0' + str;
        }

        return str;
      })
      .join('');
  };

  var rgb = [100, 0, 0];
  var colors = ['red', 'green', 'blue'];

  var gColorPicker = d3
    .select('div#slider-color-picker')
    .append('svg')
    .attr('width', 600)
    .attr('height', 400)
    .append('g')
    .attr('transform', 'translate(30,30)');

  var box = gColorPicker
    .append('rect')
    .attr('width', 100)
    .attr('height', 100)
    .attr('transform', 'translate(400,0)')
    .attr('fill', `#${num2hex(rgb)}`);

  rgb.forEach((color, i) => {
    var slider = d3
      .sliderBottom()
      .min(0)
      .max(255)
      .step(1)
      .width(300)
      .default(rgb[i])
      .displayValue(false)
      .fill(colors[i])
      .on('onchange', num => {
        rgb[i] = num;
        box.attr('fill', `#${num2hex(rgb)}`);
        d3.select('p#value-color-picker').text(`#${num2hex(rgb)}`);
      });

    gColorPicker
      .append('g')
      .attr('transform', `translate(30,${60 * i})`)
      .call(slider);
  });
  
  d3.select('p#value-color-picker').text(`#${num2hex(rgb)}`);
</script>